#!/usr/bin/groovy
@Library('test-shared-library') _
import groovy.json.JsonSlurperClassic
import java.util.Date
import java.text.SimpleDateFormat

// Job parameters
properties(
        [
                parameters(
                        [booleanParam(name: 'wasH2OUpgraded', defaultValue: true, description: "True if H2O was upgraded in this release or not"),
                         booleanParam(name: 'updateChangeLog', defaultValue: true, description: "Update change log"),
                         string(name: 'releaseFor', defaultValue: 'all', description: "For which Spark Major version" +
                                 " do a release. By default, do release for all supported released versions"),
                         booleanParam(name: 'buildConda', defaultValue: true, description: 'Build Conda'),
                         booleanParam(name: 'publishToNexus', defaultValue: true, description: 'Publish to Nexus'),
                         booleanParam(name: 'publishToS3', defaultValue: true, description: 'Publish to S3'),
                         booleanParam(name: 'publishToPiPy', defaultValue: true, description: 'Publish to PiPy'),
                         booleanParam(name: 'publishConda', defaultValue: true, description: 'Publish to Conda'),
                         booleanParam(name: 'publishDockerImages', defaultValue: true, description: 'Publish Docker Images to Docker Hub'),
                         booleanParam(name: 'releaseOnGithub', defaultValue: true, description: 'Release on Github')
                        ]
                )
        ]
)


//
// Utility methods for the pipeline
//

def getS3Path() {
    return sh(script: "./gradlew -q s3path", returnStdout: true).trim()
}

def getParallelStageDefinition(params) {
    return {
        stage("Spark ${params.spark}") {
            node("regular") {
                ws("${env.WORKSPACE}-spark-${params.spark}") {
                    cleanWs()
                    checkout scm
                    sh "git pull"
                    params.commons.withSparklingWaterDockerImage {
                        ansiColor('xterm') {
                            timestamps {
                                build()(params)
                                buildConda()(params)
                                publishToNexus()(params)
                                publishToS3()(params)
                                publishToPipy()(params)
                                publishToConda()(params)
                                publishDockerImages()(params)
                            }
                        }
                    }
                }
            }
        }
    }
}

String getVersion(params) {
    def versionLine = readFile("gradle.properties").split("\n").find() { line -> line.startsWith('version') }
    return "${versionLine.split("=")[1].replace("-SNAPSHOT", "")}-${params.spark}"
}

String getKubernetesBoundaryVersion() {
    def versionLine = readFile("gradle.properties").split("\n").find() { line -> line.startsWith('kubernetesSupportSinceSpark') }
    return versionLine.split("=")[1]
}

String getSparkVersion(sparkMajorVersion) {
    def versionLine = readFile("gradle-spark${sparkMajorVersion}.properties").split("\n").find() { line -> line.startsWith('sparkVersion') }
    return versionLine.split("=")[1]
}

String getVersionNoSuffix() {
    def versionLine = readFile("gradle.properties").split("\n").find() { line -> line.startsWith('version') }
    return versionLine.split("=")[1].replace("-SNAPSHOT", "")
}

String getNextVersionNoSuffix(params) {
    def majorVersion = getMajorVersion(params)
    def minorVersion = getMinorVersion(params)
    def patchVersion = getPatchVersion(params)
    if (params.wasH2OUpgraded.toBoolean()) {
        return "${majorVersion}.${minorVersion.toInteger() + 1}-1-SNAPSHOT"
    } else {
        return "${majorVersion}.${minorVersion}-${patchVersion.toInteger() + 1}-SNAPSHOT"
    }
}

String getMajorVersion(params) {
    def v = getVersion(params)
    def split = v.split("-")[0].split("\\.")
    return "${split[0]}.${split[1]}.${split[2]}".toString()
}

String getMinorVersion(params) {
    def v = getVersion(params)
    def split = v.split("-")[0].split("\\.")
    return "${split[3]}".toString()
}

String getPatchVersion(params) {
    def v = getVersion(params)
    def split = v.split("-")
    return "${split[1]}".toString()
}

def currentDate() {
    def date = new Date()
    def sdf = new SimpleDateFormat("yyyy-MM-dd")
    return sdf.format(date)
}

def prepareReleaseNotes(sparkMajorVersions, commons) {
    // TODO https://github.com/h2oai/sparkling-water/issues/5650
}

def updateNexRelVersion(params, commons) {
    commons.withGitPushCredentials {
        def version = getVersionNoSuffix()
        def nextVersion = getNextVersionNoSuffix(params)
        retryWithDelay(6, 10, {
            sh """
                        git config remote.origin.url "https://${GITHUB_TOKEN}@github.com/h2oai/sparkling-water.git"
                        git pull
                        if git tag --list | grep -q RELEASE-${version}; then git tag -d RELEASE-${version}; fi
                        if git ls-remote --tags origin | grep -q RELEASE-${version}; then git push --delete origin RELEASE-${version}; fi
                        ./gradlew -Prelease.useAutomaticVersion=true -Prelease.releaseVersion=${version} -Prelease.newVersion=${nextVersion} -PdoRelease release -x check
                        """
        })
    }
}

//
// Main entry point to the pipeline and definition of all stages
//
def sparkMajorVersions
node("regular") {
    cleanWs()
    checkout scm
    def commons = load 'ci/commons.groovy'
    sparkMajorVersions = commons.getSupportedSparkVersions()
    if (params.releaseFor.toString() != "all") {
        sparkMajorVersions = params.releaseFor.split(" ")
    }

    parallelStages = [:]
    sparkMajorVersions.each { version ->
        config = [:]
        params.each { k, v -> config[k] = v }
        config["spark"] = version
        config["commons"] = commons
        config["kubernetesSupported"] = commons.isKubernetesSupported(getKubernetesBoundaryVersion(), version)
        parallelStages["Spark ${version}"] = getParallelStageDefinition(config.clone())
    }

    stage('Update Change Log') {
        if (params.updateChangeLog.toBoolean()) {
            commons.withSparklingWaterDockerImage {
                ansiColor('xterm') {
                    timestamps {
                        prepareReleaseNotes(sparkMajorVersions, commons)
                    }
                }
            }
        }
    }

    parallel(parallelStages)

    stage('Update Next Release Version') {
        if (params.releaseOnGithub.toBoolean()) {
            commons.withSparklingWaterDockerImage {
                ansiColor('xterm') {
                    timestamps {
                        updateNexRelVersion(params, commons)
                    }
                }
            }
        }
    }
}

def build() {
    return { params ->
        stage('Build') {
            params.commons.withSigningCredentials {
                sh """
                    ./gradlew dist -PmakeBooklet=true -Pspark=${params.spark} -Pversion=${getVersion(params)} -PdoRelease -Psigning.keyId=${SIGN_KEY} -Psigning.secretKeyRingFile=${RING_FILE_PATH} -Psigning.password=
                    """
            }
        }
    }
}

def buildConda() {
    return { params ->
        stage('Build Conda Packages') {
            if (params.buildConda.toBoolean()) {
                def packages = [
                    [path: "py/build/conda", name: "h2o_pysparkling_${params.spark}"],
                    [path: "py-scoring/build/conda", name: "h2o_pysparkling_scoring_${params.spark}"]]
                def pythonVersions = params.commons.getSupportedPythonVersions(params.spark)
                for (packageDetails in packages) {
                    dir(packageDetails.path) {
                        for (pyVersion in pythonVersions) {
                            sh """ 
                               . ~/miniconda/etc/profile.d/conda.sh 
                               conda activate sw_env_python3.6
    
                               conda build ${packageDetails.name} --output-folder "." --no-anaconda-upload --py ${pyVersion}
    
                               PACKAGE_PATH=\$(conda build ${packageDetails.name} --py ${pyVersion} --output-folder "." --output | tail -1)
                               CURRENT_PLATFORM=\$(basename \$(dirname \$PACKAGE_PATH))
                               mkdir -p ../../../dist/build/dist/py/conda/\$CURRENT_PLATFORM
                               cp \$PACKAGE_PATH ../../../dist/build/dist/py/conda/\$CURRENT_PLATFORM/
    
                               conda convert \$PACKAGE_PATH -p linux-64 -o ../../../dist/build/dist/py/conda/
                               conda convert \$PACKAGE_PATH -p win-64 -o ../../../dist/build/dist/py/conda/
                               conda convert \$PACKAGE_PATH -p osx-64 -o ../../../dist/build/dist/py/conda/
                               
                               conda deactivate
                               """
                        }
                    }
                }
            }
        }
    }
}

def publishToNexus() {
    return { params ->
        stage('Publish to Nexus') {
            if (params.publishToNexus.toBoolean()) {
                params.commons.withNexusCredentials {
                    sh "./gradlew -Pspark=${params.spark} -Pversion=${getVersion(params)} -PnexusUsername=${NEXUS_USERNAME} -PnexusPassword=${NEXUS_PASSWORD} publishToNexus -x check"
                }
            }
        }
    }
}

def publishToS3() {
    return { params ->
        stage('Publish to S3') {
            if (params.publishToS3.toBoolean()) {
                def version = getVersion(params)
                def path = getS3Path()
                params.commons.withRootAWSCredentials {
                    sh """
                        export AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
                        export AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
                        ~/.local/bin/aws s3 sync dist/build/dist s3://h2o-release/sparkling-water/spark-${params.spark}/${path}${version} --acl public-read

                        echo UPDATE LATEST POINTER
                        echo ${version} > latest
                        echo "<head>" > latest.html
                        echo "<meta http-equiv=\\"refresh\\" content=\\"0; url=${version}/index.html\\" />" >> latest.html
                        echo "</head>" >> latest.html

                        ~/.local/bin/aws s3 cp latest s3://h2o-release/sparkling-water/spark-${params.spark}/${path}latest --acl public-read
                        ~/.local/bin/aws s3 cp latest.html s3://h2o-release/sparkling-water/spark-${params.spark}/${path}latest.html --acl public-read
                        ~/.local/bin/aws s3 cp latest.html s3://h2o-release/sparkling-water/spark-${params.spark}/${path}index.html --acl public-read
                        """
                }
            }
        }
    }
}

def publishToPipy() {
    return { params ->
        stage('Publish to PiPy') {
            if (params.publishToPiPy.toBoolean()) {
                for (projectName in ["py", "py-scoring"]) {
                    dir("$projectName/build/pkg") {
                        params.commons.withPipyCredentials {
                            sh """
                               . ~/miniconda/etc/profile.d/conda.sh
                               conda activate sw_env_python3.6
                               python setup.py sdist
                               twine upload dist/* -u $PIPY_USERNAME -p $PIPY_PASSWORD
                               conda deactivate
                               """
                        }
                    }
                }
            }
        }
    }
}

def publishCondaArtifact(arch, pkgName) {
    retryWithDelay(3, 120, {
        sh "yes | anaconda login --username ${ANACONDA_USERNAME} --password ${ANACONDA_PASSWORD}"
        sh "anaconda upload --force ../../../dist/build/dist/py/conda/${arch}/${pkgName}"
    })
}


def getCondaPkgName(pyVersion, packageName) {
    return sh(returnStdout: true, script:
            """
            CONDA_PKG_CURRENT_ARCH_PATH=\$(conda build ${packageName} --py ${pyVersion} --output-folder "." --output | tail -1)
            basename \$CONDA_PKG_CURRENT_ARCH_PATH
            """).trim()
}

def publishToConda() {
    return { params ->
        stage('Publish to Conda') {
            if (params.buildConda.toBoolean() && params.publishConda.toBoolean()) {
                def packages = [
                    [path: "py/build/conda", name: "h2o_pysparkling_${params.spark}"],
                    [path: "py-scoring/build/conda", name: "h2o_pysparkling_scoring_${params.spark}"]]
                def pythonVersions = params.commons.getSupportedPythonVersions(params.spark)
                for (packageDetails in packages) {
                    dir(packageDetails.path) {
                        params.commons.withCondaCredentials {
                            for (pyVersion in pythonVersions) {
                                def pkgName = getCondaPkgName(pyVersion, packageDetails.name)
                                for (arch in ['osx-64', 'linux-64', 'win-64']) {
                                    publishCondaArtifact(arch, pkgName)
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

def publishSparklingWaterDockerImage(String type, version, sparkMajorVersion) {
    sh """
        ./bin/build-kubernetes-images.sh ${type}
        docker tag sparkling-water-${type}:${version} h2oai/sparkling-water-${type}:${version}
        docker tag sparkling-water-${type}:${version} h2oai/sparkling-water-${type}:latest-${sparkMajorVersion}
        docker push h2oai/sparkling-water-${type}:${version}
        docker push h2oai/sparkling-water-${type}:latest-${sparkMajorVersion}
        docker rmi sparkling-water-${type}:${version}
        docker rmi h2oai/sparkling-water-${type}:${version}
        docker rmi h2oai/sparkling-water-${type}:latest-${sparkMajorVersion}
    """
}

def publishDockerImages() {
    return { params ->
        if (params.kubernetesSupported.toBoolean()) {
            stage('Publish to Docker Hub') {
                if (params.publishDockerImages.toBoolean()) {
                    params.commons.installDocker()
                    def version = getVersion(params)
                    def sparkVersion = getSparkVersion(params.spark)
                    withEnv(["SPARK_HOME=/home/jenkins/spark-${sparkVersion}-bin"]) {
                        params.commons.publishDockerImages(version) {
                            publishSparklingWaterDockerImage("scala", version, params.spark)
                            publishSparklingWaterDockerImage("r", version, params.spark)
                            publishSparklingWaterDockerImage("python", version, params.spark)
                            params.commons.removeSparkImages(sparkVersion)
                            publishSparklingWaterDockerImage("external-backend", version, params.spark)
                        }
                    }
                }
            }
        }
    }
}
